#include <GL/gl.h>
#include <iostream>

#include <cmath>
#include <vector>
#include <algorithm>
#include "spherePrimitive.h"
#include "defines/primitive.h"
#include "defines/axes.h"
#include "defines/rayState.h"
#include "vector.h"


#define START_PHI 80
#define PHI 16
#define THETA 18

SpherePrimitive::SpherePrimitive(int _id) : Object(_id){
}

SpherePrimitive::SpherePrimitive(const SpherePrimitive &_s, int _id)
    : Object(_s.getMaterial(), _s.getCenter(), _id, SPHERE, true, _s.getTransformation()), radius(_s.getRadius()){
}

SpherePrimitive::SpherePrimitive(const Vector3D &_center, double _radius, const Material &_m, int _id)
    : Object(_m, _center, _id, SPHERE, true), radius(_radius){
//    transformation.addScale(Vector3D(radius, radius, radius));
}

SpherePrimitive::SpherePrimitive(const Vector3D &_center, double _radius, const Material &_m, const Matrix &_matrix, const Matrix &_matrixI, int _id)
    : Object(_m, _center, _id, SPHERE, true, _matrix, _matrixI), radius(_radius){
}

SpherePrimitive::~SpherePrimitive(){
}

double SpherePrimitive::getRadius() const{
    return radius;
}

void SpherePrimitive::setRadius(double _r){
    radius = _r;
//    transformation.addScale(Vector3D(radius, radius, radius));
}

double SpherePrimitive::volume() const{
    return (M_PI*radius*radius*radius*4.0)/3.0;
}

double SpherePrimitive::area() const{
    return (M_PI*radius*radius*4.0);
}

void SpherePrimitive::draw(bool _wireframe, bool _useParentColor, const Material &_parentColor) const{
    if(_wireframe){
//        glDisable(GL_LIGHT0);
//        glDisable(GL_LIGHTING);

        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
        glDisable(GL_CULL_FACE);

//        glLineWidth(2);
    }

	glPushMatrix();
	glMultMatrixd(transformation.getLocalToWorld().toGlMatrix());


    Material color_ = chooseMaterial(_wireframe, _useParentColor, _parentColor);
    glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, color_.getAmbientGL());
    glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, color_.getDiffuseGL());
    glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, color_.getSpecularGL());


    glBegin(GL_TRIANGLE_FAN);
    Vector3D normal = (Vector3D(0.0, 0.0, radius)).normalize();
    glNormal3f(normal.x, normal.y, normal.z);
    glVertex3f(0.0, 0.0, radius);

    for(double i=0; i<=360.0; i = i+THETA){
        double theta = toRad(i);
        double x = radius*cos(theta)*cos(toRad(START_PHI));
        double y = radius*sin(theta)*cos(toRad(START_PHI));
        double z = radius*sin(toRad(START_PHI));

        normal = (Vector3D(x, y, z)).normalize();
        glNormal3f(normal.x, normal.y, normal.z);
        glVertex3f(x, y, z);
    }
    glEnd();


    glBegin(GL_QUAD_STRIP);
    for(double i=START_PHI; i > -START_PHI; i = i-PHI){
        for(double j=0; j<=360; j=j+THETA){

            double theta = toRad(j);
            double phi = toRad(i);
            double x = radius*cos(theta)*cos(phi);
            double y = radius*sin(theta)*cos(phi);
            double z = radius*sin(phi);

            normal = (Vector3D(x, y, z)).normalize();
            glNormal3f(normal.x, normal.y, normal.z);
            glVertex3f(x, y, z);

            phi = toRad(i - PHI);
            x = radius*cos(theta)*cos(phi);
            y = radius*sin(theta)*cos(phi);
            z = radius*sin(phi);

            normal = (Vector3D(x, y, z)).normalize();
            glNormal3f(normal.x, normal.y, normal.z);
            glVertex3f(x, y, z);
        }
    }
    glEnd();

    glBegin(GL_TRIANGLE_FAN);
    normal = (Vector3D(0.0, 0.0, -radius)).normalize();
    glNormal3f(normal.x, normal.y, normal.z);
    glVertex3f(0.0, 0.0, -radius);

    for(double i=0; i<=360.0; i = i+THETA){
        double theta = toRad(-i);
        double x = radius*cos(theta)*cos(toRad(-START_PHI));
        double y = radius*sin(theta)*cos(toRad(-START_PHI));
        double z = radius*sin(toRad(-START_PHI));
        Vector3D normal = (Vector3D(x, y, z)).normalize();

        glNormal3f(normal.x, normal.y, normal.z);

        glVertex3f(x, y, z);
    }
    glEnd();



	glPopMatrix();

    if(_wireframe){
//        glLineWidth(1);

//        glEnable(GL_LIGHT0);
//        glEnable(GL_LIGHTING);

        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
        glEnable(GL_CULL_FACE);
    }

    if(selected){
        Vector3D min, max;
        getMinMax(&min.x, &max.x, X_AXIS);
        getMinMax(&min.y, &max.y, Y_AXIS);
        getMinMax(&min.z, &max.z, Z_AXIS);

        drawBox(true, min - center, max - center);
    }
}

std::vector<Trio> SpherePrimitive::intersection(const Ray &_ray) const{
    std::vector<Trio> result;

    Vector3D origin = _ray.getOrigin();
    //inserindo o primeiro ponto que TODOS terão
    result.push_back(Trio(origin, Vector3D(1,0,0), OUT));

    //colocnado o raio para coordenadas da primitiva
    Matrix toLocalVector = transformation.getWorldToLocal();
    Matrix toLocalDirection = transformation.getWorldToLocalDirection();

    origin = (toLocalVector * origin.toHomogeneousCoordinates()).fromHomogeneousCoordinates();
    Vector3D direction = Vector3D(toLocalDirection * Vector(_ray.getDirection()));



//    std::cout << "Tem os vetores origem e direção nas coordenadas locais"<<std::endl;
//    std::cout << "Origem" <<std::endl;
//    origin.print();
//    std::cout << "Direção" <<std::endl;
//    direction.print();
//
//    std::cout << "Centro" <<std::endl;
//    center.print();
//    Vector3D originCenter = origin - center;

    double a = direction.dotProduct(direction);
    double b = direction.dotProduct(origin) * 2.0;
    double c = origin.dotProduct(origin) - (radius*radius);
    //*** aqui poderia não precisar o raio ao quadrado se tivesse a trasnformação

    double delta = (b*b) - (4.0 * a * c);

//    std::cout << "\nValores\n\tA = " <<a <<"\n\tB = " << b << "\n\tC = " << c << "\n\tDelta = " <<delta<<std::endl;
//    exit(1);


    //se for menor que zero
    //não existe interseção
    //não insere nada
    if(fabs(delta) < ERROR){ //delta igual a zero
        double t = - b / (2.0 * a);

        Matrix toWorld = transformation.getLocalToWorld();
        Matrix toLocalTranpost = transformation.getWorldToLocalDirection().transpose();

        Vector3D localP = origin + (direction * t);
        Vector3D p = (toWorld * (localP).toHomogeneousCoordinates()).fromHomogeneousCoordinates();
        Vector3D n = Vector3D(toLocalTranpost * Vector(normal(localP))).normalize();

        if(fabs(t) < ERROR){ //se t == 0
            result.back().type = ON;
            result.back().normal = n;
            result.push_back(Trio(p, n, OUT));
        }else{
            result.push_back(Trio(p, n, ON));
            result.push_back(Trio(p, n, OUT));
        }

    }else if(delta > 0.0){
        double t0 = (- b - sqrt(delta)) / (2.0 * a);
        double t1 = (- b + sqrt(delta)) / (2.0 * a);

        if(t0 <= 0.0){ //t0 é menor ou igual zero, então já começa dentro
            Matrix toWorld = transformation.getLocalToWorld();
            Matrix toLocalTranpost = transformation.getWorldToLocalDirection().transpose();

            Vector3D localP0 = origin + (direction * t0);
            Vector3D localP1 = origin + (direction * t1);
            Vector3D n0 = Vector3D(toLocalTranpost * Vector(normal(localP0))).normalize();
            Vector3D n1 = Vector3D(toLocalTranpost * Vector(normal(localP1))).normalize();

            if(fabs(t0) < ERROR){
                result.back().type = ON;
                result.back().normal = n0;
                result.push_back(Trio(result.back().point, n0, OUT));
            }
            if(t1 > 0.0){//se t1 maior que zero, então ele está pra frente
                result.back().type = IN;

                Vector3D p1 = (toWorld * (localP1).toHomogeneousCoordinates()).fromHomogeneousCoordinates();
                result.push_back(Trio(p1, n1, OUT));
            }else if(fabs(t1) < ERROR){
                result.back().type = ON;
                result.back().normal = n1;
                result.push_back(Trio(result.back().point, n1, OUT));
            }
        }else{
            //os dois valores são positivos
            Matrix toWorld = transformation.getLocalToWorld();
            Matrix toLocalTranpost = transformation.getWorldToLocalDirection().transpose();

            Vector3D localP0 = origin + (direction * t0);
            Vector3D localP1 = origin + (direction * t1);
            Vector3D n0 = Vector3D(toLocalTranpost * Vector(normal(localP0))).normalize();
            Vector3D n1 = Vector3D(toLocalTranpost * Vector(normal(localP1))).normalize();

            Vector3D p0 = (toWorld * (localP0).toHomogeneousCoordinates()).fromHomogeneousCoordinates();
            result.push_back(Trio(p0, n0, IN));

            Vector3D p1 = (toWorld * (localP1).toHomogeneousCoordinates()).fromHomogeneousCoordinates();
            result.push_back(Trio(p1, n1, OUT));

        }
    }
    return result;
}

void SpherePrimitive::getMinMax(double *_min, double *_max, unsigned int _axis) const{
    switch(_axis){
        case X_AXIS:{
            *_min = center.x - radius;
            *_max = center.x + radius;
            break;
        }
        case Y_AXIS:{
            *_min = center.y - radius;
            *_max = center.y + radius;
            break;
        }
        case Z_AXIS:{
            *_min = center.z - radius;
            *_max = center.z + radius;
            break;
        }
    }
}

Vector3D SpherePrimitive::normal(const Vector3D &_p) const{
    return (_p/* - center*/).normalize();
}
